package com.chenju.invokechainservice.service.impl;

import com.chenju.invokechainservice.adapter.MethodNodeAdapter;
import com.chenju.invokechainservice.adapter.SpringBeanClassAdapter;
import com.chenju.invokechainservice.common.enums.EasyCode;
import com.chenju.invokechainservice.component.BuildComponent;
import com.chenju.invokechainservice.domain.TAopMethod;
import com.chenju.invokechainservice.mybatis.mapper.TAopMethodMapper;
import com.chenju.invokechainservice.service.TAopService;
import com.chenju.invokechainservice.test.PointcutParser;
import com.chenju.invokechainservice.utils.FunnyFileUtils;
import com.chenju.invokechainservice.utils.FunnyStringUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class TAopServiceImplement implements TAopService {
    @Autowired
    private TAopMethodMapper aopMethodMapper;

    @Autowired
    private BuildComponent build;

    @Override
    public void saveAopMethod(TAopMethod aopMethod) {
        aopMethodMapper.save(aopMethod);
    }

    @Override
    public TAopMethod findAopMethodByMethodName(String methodName) {
        return aopMethodMapper.findByMethodName(methodName);
    }

    @Override
    public String findAopMethodExpressionByPointcutMethodName(String pointcutMethodName) {
        return aopMethodMapper.findExpressionByPointcutMethodName(pointcutMethodName);
    }

    @Override
    public List<TAopMethod> findAll() {
        return aopMethodMapper.findAll();
    }

    @Override
    public void updateAdviceForMethods(TAopMethod aopMethod) {
        aopMethodMapper.updateAdviceForMethods(aopMethod);
    }

    @Override
    public Map<String, File> findSpringBeanClassMap(String codePath) {
        Map<String, File> springBeanClassMap = new HashMap<>();

        // 区分gradle项目和maven项目
        List<String> modulePaths = FunnyFileUtils.findModulePaths(codePath, build.getBuildTool());

        for (String modulePath : modulePaths){
            String classesFilePath = FunnyStringUtils.getClassesFilePath(build.getClassesDir());
            String classesPath = FunnyStringUtils.joinPath(modulePath, classesFilePath);

            FileFilter classFileFilter = new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return file.isFile() && file.getName().endsWith(".class");
                }
            };
            List<File> classFiles = FunnyFileUtils.listFiles(new File(classesPath), classFileFilter);

            for (File classFile : classFiles){
                try {
                    ClassReader cr = new ClassReader(new FileInputStream(classFile));
                    SpringBeanClassAdapter sbca = new SpringBeanClassAdapter();
                    cr.accept(sbca, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
                    String className = parseSingleFileBeanClass(sbca);
                    if (className != null){
                        springBeanClassMap.put(className, classFile);
                    }
                    parseSingleFileAdvice(sbca);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return springBeanClassMap;
    }

    @Override
    public String parseSingleFileBeanClass(SpringBeanClassAdapter sbca) {
        if (sbca.isSpringBean()){
            return FunnyStringUtils.findDotString(sbca.name);
        }
        return null;
    }

    @Override
    public void parseSingleFileAdvice(SpringBeanClassAdapter sbca) {
        if (sbca.isAspect()) {
            List<MethodNodeAdapter> methods = sbca.methods;
            for (MethodNodeAdapter method : methods){
                String methodName = FunnyStringUtils.findDotString(sbca.name + "/" + method.name);
                String adviceType = null;
                String pointcutExpression = null;
                if (method.isAdvice){
                    adviceType = method.adviceType.toUpperCase();
                    List<AnnotationNode> visibleAnnotations = method.visibleAnnotations;
                    for (AnnotationNode annotationNode : visibleAnnotations){
                        if (EasyCode.adviceAnnotationList.contains(FunnyStringUtils.findDotString(Type.getType(annotationNode.desc).getClassName()))){
                            pointcutExpression = annotationNode.values.get(annotationNode.values.indexOf("value") + 1).toString();
                        }
                            System.out.println(annotationNode);
                    }
                }
                saveAopMethod(new TAopMethod(methodName, adviceType, pointcutExpression));
            }
        }
    }

    @Override
    public List<String> parsePointcutExpression(String pointcutExpression, Class<?> targetClass) {
        List<String> adviceForMethodList = new ArrayList<>();

        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(pointcutExpression);

        MethodMatcher methodMatcher = pointcut.getMethodMatcher();

        // 这里可以遍历目标类的方法，判断是否匹配切点表达式
        Method[] methods = targetClass.getDeclaredMethods();
        for (Method method : methods) {
            if (methodMatcher.matches(method, targetClass)) {
                adviceForMethodList.add(method.getName());
            }
        }

        return adviceForMethodList;
    }

    @Override
    public void parseProjectPointcut(String codePath) {
        Map<String, File> springBeanClassMap = findSpringBeanClassMap(codePath);

        List<TAopMethod> allTAopMethods = findAll();
        for (TAopMethod aopMethod : allTAopMethods){
            List<String> adviceForMethods = new ArrayList<>();
            springBeanClassMap.forEach((key, value) -> {
                // 读取class文件的字节码
                byte[] classBytes = new byte[0];
                try {
                    classBytes = Files.readAllBytes(value.toPath());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                // 使用自定义的ClassLoader加载类
                Class<?> targetClass = new CustomClassLoader().defineClass(classBytes);
                System.out.println(key +": " + value.getAbsolutePath());
                adviceForMethods.addAll(parsePointcutExpression(aopMethod.getPointcutExpression(), targetClass));
            });
            aopMethod.setAdviceForMethods(adviceForMethods);
            updateAdviceForMethods(aopMethod);
        }

    }

    static class CustomClassLoader extends ClassLoader {
        public Class<?> defineClass(byte[] classBytes) {
            return defineClass(null, classBytes, 0, classBytes.length);
        }
    }

}
